package com.jltx.zeus.viewmodel

import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.consumeEach


/**
 * @author jltxseo
 *         Created by junlintianxia on 2020/05/10.
 */
internal interface CoroutineDelegate : CoroutineEvent {

    val actionEvent: MutableLiveData<BaseActionEvent>


    fun <T> execute(callback: ViewModelCallback<T>?, block: suspend () -> T): Job {
        return execute(callback, showLoading = false, block = block)
    }


    fun <T> executeLoading(callback: ViewModelCallback<T>?, block: suspend () -> T): Job {
        return execute(callback, showLoading = true, block = block)
    }

    fun <T> execute(
        callback: ViewModelCallback<T>?,
        showLoading: Boolean,
        block: suspend () -> T
    ): Job {
        return launchUI {
            try {
                if (showLoading) {
                    actionEvent.postValue(ShowLoadingEvent(""))
                }
                callback?.onStart()
//                val asyncIO = asyncIO {
//                    block()
//                }
//                val response = asyncIO.await()
                val response = withIO {
                    block()
                }

                callback?.onSuccess(response)
                withIO {
                    callback?.onSuccessIO(response)
                }
            } catch (throwable: Throwable) {
                handleException(throwable, callback)
            } finally {
                try {
                    callback?.onFinally()
                } finally {
                    if (showLoading) {
                        actionEvent.postValue(DismissLoadingEvent)
                    }
                }
            }
        }
    }

    fun <T> executeDeferred(
        callback: ViewModelCallback<T>?,
        block: suspend CoroutineScope.() -> Deferred<T>
    ): Job {
        return executeDeferred(callback, showLoading = false, block = block)
    }

    fun <T> executeDeferredLoading(
        callback: ViewModelCallback<T>?,
        block: suspend CoroutineScope.() -> Deferred<T>
    ): Job {
        return executeDeferred(callback, showLoading = true, block = block)
    }

    fun <T> executeDeferred(
        callback: ViewModelCallback<T>?,
        showLoading: Boolean,
        block: suspend CoroutineScope.() -> Deferred<T>
    ): Job {
        return launchUI {
            try {
                if (showLoading) {
                    actionEvent.postValue(ShowLoadingEvent(""))
                }
                callback?.onStart()
                val asyncIO = block()
                val response = asyncIO.await()
                callback?.onSuccess(response)
                withIO {
                    callback?.onSuccessIO(response)
                }
            } catch (throwable: Throwable) {
                handleException(throwable, callback)
            } finally {
                try {
                    callback?.onFinally()
                } finally {
                    if (showLoading) {
                        actionEvent.postValue(DismissLoadingEvent)
                    }
                }
            }
        }
    }

    fun <T> executeProduce(
        callback: ViewModelCallback<T>?,
        block: suspend CoroutineScope.() -> ReceiveChannel<ProduceResult<T>>
    ): Job {
        return executeProduce(callback, showLoading = false, block = block)
    }

    fun <T> executeProduceLoading(
        callback: ViewModelCallback<T>?,
        showLoading: Boolean,
        block: suspend CoroutineScope.() -> ReceiveChannel<ProduceResult<T>>
    ): Job {
        return executeProduce(callback, showLoading = true, block = block)
    }

    fun <T> executeProduce(
        callback: ViewModelCallback<T>?,
        showLoading: Boolean,
        block: suspend CoroutineScope.() -> ReceiveChannel<ProduceResult<T>>
    ): Job {
        return launchUI {
            try {
                if (showLoading) {
                    actionEvent.postValue(ShowLoadingEvent(""))
                }
                callback?.onStart()
                val channel = block()

                channel.consumeEach { produceResult ->
                    when (produceResult.status) {
                        ProduceResultStatus.STATUS_SUCCESS -> {
                            callback?.let {
                                produceResult.data?.apply {
                                    it.onSuccess(this)
                                }
                                withIO {
                                    produceResult.data?.apply {
                                        it.onSuccessIO(this)
                                    }
                                }
                            }
                        }
                        ProduceResultStatus.STATUS_FAIL -> {
                            callback?.let {
                                produceResult.error?.apply {
                                    handleException(this, callback)
                                }
                            }
                        }
                        else -> {
                        }
                    }
                }
            } catch (throwable: Throwable) {
                handleException(throwable, callback)
            } finally {
                try {
                    callback?.onFinally()
                } finally {
                    if (showLoading) {
                        actionEvent.postValue(DismissLoadingEvent)
                    }
                }
            }
        }
    }

    private suspend fun handleException(exception: Throwable, callback: BaseViewModelCallback?) {
        callback?.let {
            withMain {
                callback.onFail(exception)
            }
        }
    }


}